package kode.boot.file.local;

import kode.base.core.util.*;
import kode.boot.file.api.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.awt.*;
import java.io.*;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;

/**
 * LocalFileService
 *
 * @author Stark
 * @date 2019/1/1
 */
@Slf4j
public class LocalFileService<ID> extends AbstractFileService<FileMetadata<ID>, ID> {

	@Getter
	@Setter
	private LocalFileProperties localFileProperties = new LocalFileProperties();
	/**
	 * 缓存区大小，通常和文件系统 block 大小有关。默认通常为 4096/8192
	 */
	private int bufferSize = 4096;

	public LocalFileService() {
	}

	public LocalFileService(LocalFileProperties properties) {
		this.localFileProperties = properties;
	}

	// read file
	//-----------------------

	private File checkFileLocation(String location) {
		String basePath = localFileProperties.getBasePath();
		File file = new File(basePath.concat(location));
		if (!file.exists()) {
			throw new FileException("file of the location does not exists.");
		}

		return file;
	}

	@Override
	public long getFileSize(String location) throws IOException {
		File file = checkFileLocation(location);
		return Files.size(Path.of(file.getAbsolutePath()));
	}

	@Override
	public InputStream readFile(String location) {
		File file = checkFileLocation(location);

		try {
			return new FileInputStream(file);
		} catch (FileNotFoundException e) {
			throw new FileException(e);
		}
	}

	// save file
	//-----------------------

	/**
	 * save file
	 *
	 * <ul>
	 * <li>如果 metadata 中指定了 location，将传入的 location 作为文件保存的路径进行保存</li>
	 * </ul>
	 *
	 * @param inputStream  输入流
	 * @param fileMetadata 文件元数据
	 * @param validator    文件验证
	 * @throws IOException 流 io 异常时抛出
	 */
	@Override
	public void saveFile(InputStream inputStream, FileMetadata<ID> fileMetadata, FileValidator validator) throws IOException {
		Validate.notNull(inputStream, "file input stream must not be null.");
		Validate.notNull(fileMetadata, "fileMetadata must not be null.");
		Validate.notBlank(fileMetadata.getName(), "fileMetadata.name must not be null/empty/blank.");

		// 如果传入的 fileMetadata 不包含文件信息，返回时设置文件信息
		// 如果传入设置了 location，检查对应文件是否存在，不存在写入，存在覆盖

		String fileName = fileMetadata.getName();
		String basePath = localFileProperties.getBasePath();
		File path = new File(basePath);
		if (!path.exists()) {
			path.mkdirs();
		}

		int fileSize = inputStream.available();
		if (fileMetadata.getSize() == 0) {
			fileMetadata.setSize((long) fileSize);
		}

		// file validation
		InputStream bufferedInputStream = inputStream;
		if (validator != null) {
			if (validator.parseAsImage()) {
				bufferedInputStream = new BufferedInputStream(inputStream, fileSize);
				bufferedInputStream.mark(fileSize + 1);

				Dimension dimension = getImageDimension(bufferedInputStream);
				if (dimension == null) {
					throw new FileValidateException("Unsupported image input stream");
				}

				fileMetadata.setWidth(dimension.width);
				fileMetadata.setHeight(dimension.height);
				fileMetadata.setProperty("width", dimension.width);
				fileMetadata.setProperty("height", dimension.height);

				bufferedInputStream.reset();
			}

			validator.accept(fileMetadata);
		}

		// save file
		String finalFolder = generateFinalFolderPath(fileMetadata);
		String finalName = generateFinalFileName(fileMetadata);
		String finalFullPath = basePath.concat(finalFolder);
		String finalFullName = finalFullPath.concat(File.separator).concat(finalName);
		path = new File(finalFullPath);
		if (!path.exists()) {
			path.mkdirs();
		}

		try {
			try (FileOutputStream os = new FileOutputStream(finalFullName)) {
				int readTotal = 0, bytesRead;
				byte[] buff = new byte[bufferSize];
				log.trace("Saving '{}', total: {} bytes", fileName, fileSize);
				while ((bytesRead = bufferedInputStream.read(buff)) != -1) {
					os.write(buff, 0, bytesRead);
					readTotal += bytesRead;
					log.trace("Saving '{}', saved/total: {}/{} bytes", fileName, readTotal, fileSize);
				}

				log.info("Saving '{}' -> '{}' finished.", fileName, finalFullName);
			}
		} catch (IOException ex) {
			Files.deleteIfExists(Path.of(finalFullName));
			throw ex;
		}

		String location = FileUtil.slashPath(finalFolder.concat(File.separator).concat(finalName));
		fileMetadata.setLocation(location);
		if (fileMetadata.getContentType() == null) {
			try {
				fileMetadata.setContentType(Files.probeContentType(FileSystems.getDefault().getPath(finalFullName)));
			} catch (Exception ignore) {
			}
		}
	}


	// delete file
	//-----------------------

	@Override
	public boolean deleteFile(String location) {
		String filePath = localFileProperties.getBasePath() + location;
		File file = new File(filePath);
		if (file.exists()) {
			return file.delete();
		}

		return false;
	}


	/**
	 * folder path starts with, start with separator /
	 */
	protected String generateFinalFolderPath(FileMetadata<ID> fileMetadata) {
		String location = fileMetadata.getLocation();
		if (!StringUtil.isBlank(location)) {
			if (location.startsWith(File.separator)) {
				return location;
			} else {
				return File.separator.concat(location);
			}
		}

		return File.separator.concat(RandomUtil.randomHex(2));
	}

	/**
	 * file name with extension, no file separator
	 */
	protected String generateFinalFileName(FileMetadata<ID> fileMetadata) {
		String fileName = UuidUtil.uuidShortCompressed();
		String fileExt = fileMetadata.getExtension();
		if (!StringUtil.isEmpty(fileExt)) {
			fileName = fileName.concat(fileExt);
		}

		return fileName;
	}
}
